/* Copyright (c) 2011-2013 ISP RAS, Moscow. For more information please write to

      mermakov@ispras.ru
      svartanov@ispras.ru

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package ru.ispras.coffeemachine.instrument;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import ru.ispras.coffeemachine.target.Concurrency;
import ru.ispras.coffeemachine.target.Daemon;
import ru.ispras.coffeemachine.util.Executor;
import ru.ispras.coffeemachine.util.Logger;
import ru.ispras.coffeemachine.util.Options;
import ru.ispras.coffeemachine.util.Output;
import ru.ispras.coffeemachine.util.Util;

/**
 * Classfile set instrumentation, updating and minor technical issues
 *
 * @author Sergey Vartanov (me@enzet.ru)
 * @version 1.2.0
 * @since 2011-09-12
 */
public class Espresso
{
	/** List for all class files */
	private final ClassfileSet classfiles;

	/**
	 * Adding classfiles from options
	 */
	public Espresso()
	{
		classfiles = Options.classfiles;
	}

	/**
	 * Writes instrumented class files into corresponding directory/Jar files.
	 *
	 * @throws IOException
	 */
	public void update() throws IOException
	{
		Output trace;

		Logger.info("Starting class file update.");
		for (Classfile classfile : classfiles)
		{
			classfile.update();
		}
		Map<String, Set<String>> jarPackages = classfiles.getJarPackages();
		for (Entry<String, Set<String>> entry : jarPackages.entrySet()) {
			for (String jarPackage : entry.getValue()) {
				if (jarPackage.equals("")) {
					Executor.execute("jar -uf " + Options.instrumentationDir + entry.getKey() +
					                 " -C " + Options.instrumentationDir + " *.class");
					Executor.execute("rm " + Options.instrumentationDir + "*.class");
				} else {
					Executor.execute("jar -uf " + Options.instrumentationDir + entry.getKey() +
			                 " -C " + Options.instrumentationDir + " " + jarPackage);
					Util.deleteFileRecursive(Options.instrumentationDir + jarPackage);
				}
			}
		}
		Logger.info("Class file update successful!");

		String targetDirName = "ru/ispras/coffeemachine/target/";
		File targetDir = new File(Options.instrumentationDir + "/" + targetDirName);
		targetDir.mkdirs();
		trace = Executor.execute("cp -r " + Options.binDir + "/" + targetDirName + " " + Options.instrumentationDir + "/ru/ispras/coffeemachine");
		trace.checkErrors();

		Mokka.setDaemonOutput();
		if ((Options.concurrencyEventsFile != null) && Options.concurrencyEnabled)
		{
			Mokka.setConcurrencyFile(Options.concurrencyEventsFile);
		}

		String strippedJar = Options.targetJar;
		if (strippedJar != null) {
			int slashPos = strippedJar.lastIndexOf('/');
			if (slashPos != -1) {
				strippedJar = strippedJar.substring(slashPos);
			}
			trace = Executor.execute("jar -uf " + Options.instrumentationDir + "/" + strippedJar + " -C " + Options.instrumentationDir + " " + targetDirName);
		}
	}

	/**
	 * Determine the list of required classes
	 */
	void getCalls() throws Exception
	{
	}

	/**
	 * Runs instrumentation for specified class files.
	 */
	public void instrument() throws Exception
	{
		if (Options.targetClassfile != null)
		{
			classfiles.add(new Classfile(Options.targetClassfile, null));
		}
		// TODO getCalls();

		classfiles.addParentMethods();

		if (Options.concurrencyEnabled)
		{
			Invoker.init(Concurrency.class);
		}
		if (Options.stpEnabled)
		{
			Invoker.init(Daemon.class);
		}
		Logger.verbose("Starting instrumentation.");

		for (Classfile classfile : classfiles)
		{
			try
			{
				classfile.getMokka().instrument();
			}
			catch (ClassNotFoundException e)
			{
				Logger.error("Espresso: cannot find class " + classfile.getName());
			}
		}
		Logger.verbose(classfiles.size() + " classfiles instrumented!");

		if (Options.printMethodsCoverage)
		{
			Invoker.printMethodsCoverage();
		}
	}
}
